47 创建N维数组
47.1 引言NumPy数组的数学基础
理论背景:张量(Tensor)与多维数组
NumPy(Numerical Python)的核心数据结构是ndarray(N-dimensional array),它实现了张量(Tensor)的概念。从数学的角度:
- 标量(Scalar): 0维张量,如\(5\)
- 向量(Vector): 1维张量,如\([1, 2, 3]\)
- 矩阵(Matrix): 2维张量,如\([[1, 2], [3, 4]]\)
- 高维张量: 3维及以上,如时间序列数据立方体
数学表示:张量索引
对于\(d\)维张量\(\mathbf{A}\),元素索引为: \[ \mathbf{A}_{i_1, i_2, \ldots, i_d} \]
其中每个索引\(i_k\)的范围是\(0 \leq i_k < n_k\)(\(n_k\)是该维度的大小)。
NumPy数组的内存模型:
| 特性 | NumPy数组 | Python列表 |
|---|---|---|
| 内存布局 | 连续内存块 | 分散的对象引用 |
| 数据类型 | 同构(所有元素类型相同) | 异构(可混合) |
| 元素访问 | O(1)直接访问 | O(1)通过引用 |
| 内存效率 | 紧凑存储(如int32占4字节) | 每个对象约28字节开销 |
| 计算性能 | 向量化操作(C层面) | 解释执行(Python层面) |
补充说明:为什么NumPy如此重要?
NumPy提供了Python中缺失的高性能数值计算能力: 1. 向量化运算: 避免Python循环,利用CPU的SIMD指令 2. 广播机制: 自动对齐不同形状的数组 3. C语言底层: 核心代码用C实现,速度接近编译语言 4. 丰富的函数库: 线性代数、傅里叶变换、随机数生成等
47.2 创建一维数组
平台任务解答代码
以下代码与教学平台任务要求完全一致:
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import numpy as np #导入NumPy模块并且用英文缩写np
price_amazon= np.array([170.23,170.10,177.59,177.06,178.22]) #直接创建一维数组
type (price_amazon) # 查看price_amazon的数据类型
price_app1e=[221.27,221.72,224.72,226.05,225.89] # 定义列表price_app1e
price_apple=np.array(price_app1e) #将列表转换为一维数组
type(price_apple) # 查看price_apple的数据类型
#任务二
import numpy as np
data1 = [170.23,170.10,177.59,177.06,178.22] # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89] # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53] # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67] # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53] # 定义列表data5
price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组
print(price_array.shape) #查看数组的形状
print(price_array.ndim) #查看数组的维度
print(price_array.size) #查看数组的元素个数
print(price_array.dtype) #查看数组中的元素类型
#任务三
import numpy as np
price_app1e=[221.27,221.72,224.72,226.05,225.89] # 定义列表price_app1e
n=price_array.size #变量n赋值为数组的元素个数
data1 = [170.23,170.10,177.59,177.06,178.22] # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89] # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53] # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67] # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53] # 定义列表data5
price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组
n=price_array.size #变量n赋值为数组的元素个数
x=np.arange(n+1) #生成整数序列
m=40 # 设置数组长度参数为40
y=np.linspace(price_apple[0],price_apple[-1],m)#生成起始值是苹果公司股票5月13日收盘价、终止值是5月17日收盘价且元素数量40的等差序列
print(y) # 输出变量y的值
#任务四
import numpy as np
price_app1e=[221.27,221.72,224.72,226.05,225.89] # 定义列表price_app1e
price_amazon=np.array([1822.68,1840,12,1871.15,1907.57,1869.00]) # 创建NumPy数组price_amazon
zero_arrayl=np.zeros_like(price_amazon)#快速生成元素为零的一维数组
zero_array2=np.zeros_like(price_array)#快速生成元素为零的二维数组
one_arrayl=np.ones_like(price_amazon)#快速生成元素等于1的一维数组
one_array2=np.ones_like(price_array) #快速生成元素等于1的二维数组
print(zero_arrayl) # 输出变量zero_arrayl的值
print(zero_array2) # 输出变量zero_array2的值
print(one_arrayl) # 输出变量one_arrayl的值
print(one_array2) # 输出变量one_array2的值# 导入NumPy库
# np是NumPy的通用别名,符合社区约定
import numpy as np
# 方法1:从Python列表创建
# np.array()将列表转换为NumPy数组
# [1, 2, 3, 4, 5]是Python列表
arr1 = np.array([1, 2, 3, 4, 5])
# 方法2:使用arange()创建
# np.arange()类似于Python的range(),但返回NumPy数组
# arange(10)生成[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr2 = np.arange(10)
# 方法3:使用linspace()创建等间距数列
# np.linspace(start, stop, num)生成从start到stop的num个等间距点
# 包含起始值和结束值
# linspace(0, 1, 5)生成[0., 0.25, 0.50, 0.75, 1.00]
arr3 = np.linspace(0, 1, 5)
# 输出数组内容
print('arange:', arr2)
print('linspace:', arr3)代码深度解析:
arange() vs linspace()的区别:
# arange(): 指定步长 # 语法: np.arange(start, stop, step) arr_arange = np.arange(0, 1, 0.2) print(arr_arange) # [0., 0.2, 0.4, 0.6, 0.8] (不包含1.0) # linspace(): 指定元素个数 # 语法: np.linspace(start, stop, num) arr_linspace = np.linspace(0, 1, 6) print(arr_linspace) # [0., 0.2, 0.4, 0.6, 0.8, 1.0] (包含1.0) # 金融应用:生成时间序列 # linspace确保包含起始和结束时间点 # 适合生成固定频率的时间索引数据类型的自动推断:
# NumPy自动推断数据类型 int_arr = np.array([1, 2, 3]) print(int_arr.dtype) # int32或int64(取决于系统) float_arr = np.array([1.0, 2.0, 3.0]) print(float_arr.dtype) # float64 # 显式指定数据类型 float_arr_int32 = np.array([1, 2, 3], dtype=np.float32) print(float_arr_int32.dtype) # float32 # 为什么要指定数据类型? # - 内存控制: float32比float64节省一半内存 # - 精度需求: 某些应用需要更高精度 # - 兼容性: 与C/C++库交互时数组与列表的本质区别:
# 列表:存储对象的引用 python_list = [1, 2, 3] print(python_list[0]) # 1 python_list[0] = 100 print(python_list) # [100, 2, 3] # NumPy数组:存储实际的数值 numpy_arr = np.array([1, 2, 3]) print(numpy_arr[0]) # 1 numpy_arr[0] = 100 print(numpy_arr) # [100, 2, 3] # 但NumPy数组的元素类型必须一致 # 这与列表不同 mixed_list = [1, 'hello', 3.14] # 合法 # mixed_arr = np.array([1, 'hello', 3.14]) # 全部转为字符串金融应用:生成交易日序列:
import pandas as pd # 生成252个交易日(假设一年) # 使用arange生成索引 trading_days = np.arange(252) # 生成日期范围 dates = pd.date_range('2024-01-01', periods=252, freq='B') # 截取实际交易日(排除节假日) # 实际应用中会少于252天 actual_dates = dates[:len(trading_days)] print(f'前10个交易日: {actual_dates[:10]}')数组的内存占用:
import sys # 比较内存占用 python_list = list(range(100000)) numpy_arr = np.arange(100000) list_size = sys.getsizeof(python_list) arr_size = sys.getsizeof(numpy_arr) print(f'列表大小: {list_size / 1024:.2f} KB') print(f'数组大小: {arr_size / 1024:.2f} KB') # NumPy数组的优势在大规模数据时更明显 # 100万个整数: # - 列表: ~28 MB (每个int对象约28字节) # - 数组: ~8 MB (每个int32占4字节)
47.3 创建二维数组
# 方法1:从列表的列表创建
# 列表的列表代表二维结构
# 内层每个列表代表矩阵的一行
arr2d = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
# 方法2:使用reshape()重塑数组
# 首先创建一个一维数组: [0, 1, 2, ..., 10, 11]
arr_flat = np.arange(12)
# reshape()改变数组的形状,不改变数据
# (3, 4)表示重塑为3行4列的矩阵
# 元素总数必须匹配(3×4=12)
arr_3x4 = arr_flat.reshape(3, 4)
print('2D数组(3x3):')
print(arr2d)
print(f'\n3x4数组:')
print(arr_3x4)代码深度解析:
数组形状的理解:
# 数组的shape属性返回各维度的大小 arr = np.array([[1, 2, 3], [4, 5, 6]]) print(arr.shape) # (2, 3) - 2行3列 # 形状的直观理解: # 第0维(行): 有2个元素 # 第1维(列): 每行有3个元素 # # 可视化为: # [[1, 2, 3], <- 第0行 # [4, 5, 6]] <- 第1行 # 访问特定位置的元素 print(arr[0, 1]) # 2 - 第0行,第1列 print(arr[1, 2]) # 6 - 第1行,第2列reshape()的规则:
# 规则:重塑前后的元素总数必须相等 arr = np.arange(12) # 12个元素 # 合法的重塑 print(arr.reshape(3, 4)) # 3×4=12 ✓ print(arr.reshape(4, 3)) # 4×3=12 ✓ print(arr.reshape(2, 6)) # 2×6=12 ✓ # print(arr.reshape(5, 3)) # 5×3=15 ✗ 错误! # -1作为自动推断 # reshape(3, -1)表示:3行,列数自动计算 print(arr.reshape(3, -1)) # (3, 4) - 推断出4列 print(arr.reshape(-1, 4)) # (3, 4) - 推断出3行 # 为什么使用-1? # - 方便:不需要手动计算 # - 灵活:修改一个维度时另一个自动调整转置操作:
# .T属性或transpose()方法实现矩阵转置 arr = np.array([[1, 2, 3], [4, 5, 6]]) print('原数组:') print(arr) print(f'形状: {arr.shape}') print('\n转置后:') print(arr.T) print(f'形状: {arr.T.shape}') # 转置的数学意义: # 如果arr是m×n矩阵 # 则arr.T是n×m矩阵 # 元素[i, j]变为元素[j, i]金融应用:收益率矩阵:
# 假设有3只股票,5个交易日的收益率 # 每行代表一只股票,每列代表一个交易日 returns_3x5 = np.array([ [0.01, -0.02, 0.03, 0.01, -0.01], # 股票1 [0.02, 0.01, -0.01, 0.03, 0.02], # 股票2 [-0.01, 0.02, 0.01, -0.02, 0.01] # 股票3 ]) # 计算每只股票的平均收益率 # axis=1表示沿第1维(列)操作,即对每行求平均 avg_returns = returns_3x5.mean(axis=1) print(f'平均收益率: {avg_returns}') # 计算每天的收益率均值 # axis=0表示沿第0维(行)操作,即对每列求平均 daily_avg = returns_3x5.mean(axis=0) print(f'每日均值: {daily_avg}')多维数组的可视化理解:
# 3维数组:时间序列数据立方体 # 形状:(股票数, 交易日数, 数据点数) # 例如: (100, 252, 4) # - 100只股票 # - 每只股票252个交易日的数据 # - 每天4个数据点(开盘、最高、最低、收盘) arr_3d = np.random.randn(100, 252, 4) # 提取第一只股票的所有数据 stock_0 = arr_3d[0, :, :] # shape: (252, 4) # 提取第一天的所有股票收盘价(假设是第3列) close_day_0 = arr_3d[:, 0, 3] # shape: (100,) # 提取第一只股票第一天的数据 stock_0_day_0 = arr_3d[0, 0, :] # shape: (4,)
47.4 创建特殊数组
# 1. 全0数组
# np.zeros()创建所有元素为0的数组
# 参数可以是整数(一维)或元组(多维)
zeros = np.zeros((2, 3))
print('全0数组(2x3):')
print(zeros)
# 2. 全1数组
# np.ones()创建所有元素为1的数组
ones = np.ones((3, 4))
print('\n全1数组(3x4):')
print(ones)
# 3. 单位数组(单位矩阵)
# np.eye(n)创建n×n的单位矩阵
# 单位矩阵:对角线为1,其余为0
identity = np.eye(3)
print('\n单位矩阵(3x3):')
print(identity)
# 4. 对角矩阵
# np.diag()创建对角矩阵或提取对角元素
diag = np.diag([1, 2, 3])
print('\n对角矩阵:')
print(diag)
# 5. 随机数组
# np.random.randn()生成标准正态分布的随机数
# 均值0,标准差1
random_arr = np.random.randn(2, 3)
print('\n随机数组(2x3):')
print(random_arr)代码深度解析:
特殊数组的数学意义:
# 全0数组:零矩阵 # 在线性代数中,零矩阵O满足: O + A = A zero_matrix = np.zeros((3, 3)) # 全1数组:所有元素为1的矩阵 ones_matrix = np.ones((3, 3)) # 单位矩阵:I # 单位矩阵在矩阵乘法中充当1的角色: I·A = A·I = A I = np.eye(3) # 验证单位矩阵的性质 A = np.array([[1, 2], [3, 4]]) print(np.dot(I, A)) # 等于A print(np.dot(A, I)) # 等于A对角矩阵的应用:
# 对角矩阵在协方差矩阵中的应用 # 对角线是方差,非对角线是协方差 # 假设3只资产,彼此独立 # 对角线是各资产的方差 variances = [0.01, 0.02, 0.015] # 1%, 2%, 1.5% cov_matrix = np.diag(variances) print('协方差矩阵(独立资产):') print(cov_matrix) # [[0.01, 0. , 0. ], # [0. , 0.02 , 0. ], # [0. , 0. , 0.015 ]] # 金融应用:计算投资组合方差 # 方差 = w^T · Σ · w # 其中w是权重向量,Σ是协方差矩阵 weights = np.array([0.4, 0.3, 0.3]) portfolio_var = np.dot(weights, np.dot(cov_matrix, weights)) print(f'投资组合方差: {portfolio_var:.6f}')随机数生成:
# 标准正态分布: N(0, 1) # mean=0, std=1 standard_normal = np.random.randn(1000) # 均匀分布: U(0, 1) # [0, 1]区间上的均匀分布 uniform = np.random.rand(1000) # 正态分布: N(μ, σ²) # 均值μ,标准差σ normal = np.random.normal(loc=0.001, scale=0.02, size=1000) # loc:均值, scale:标准差 # 金融应用:模拟股票收益率 # 假设日收益率服从正态分布 # 均值0.05%(年化约13%),标准差2%(年化约32%) daily_returns = np.random.normal(0.0005, 0.02, 252) # 计算年化收益率 # 简单近似: 年化收益率 ≈ 日收益率 × 252 annualized_return = daily_returns.mean() * 252 print(f'年化收益率: {annualized_return:.2%}') # 年化波动率 # 年化波动率 = 日波动率 × √252 annualized_vol = daily_returns.std() * np.sqrt(252) print(f'年化波动率: {annualized_vol:.2%}')单位矩阵的特殊性质:
I = np.eye(3) # 性质1: I的行列式为1 print(f'行列式: {np.linalg.det(I):.0f}') # 性质2: I的迹(trace,对角元素之和)为维度 print(f'迹: {np.trace(I)}') # 1+1+1 = 3 # 性质3: I的特征值全为1 eigenvalues, _ = np.linalg.eig(I) print(f'特征值: {eigenvalues}') # 性质4: I是正定矩阵 # 正定矩阵:所有特征值>0 print(np.all(eigenvalues > 0)) # True空数组与标量扩展:
# 空数组 empty = np.array([]) print(f'空数组形状: {empty.shape}') # (0,) # 标量扩展(广播) scalar = 5 arr = np.array([1, 2, 3]) result = scalar + arr # 标量5扩展为[5, 5, 5] print(f'标量扩展: {result}') # [6, 7, 8] # 这种机制称为"广播"(Broadcasting) # 它使得不同形状的数组可以进行运算
47.5 数组属性
# 创建一个2×3的数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
# 1. shape属性:数组形状
# 返回元组,表示各维度的大小
print('形状:', arr.shape) # (2, 3) - 2行3列
# 2. ndim属性:数组维度
# 返回整数,表示维度的数量
print('维度:', arr.ndim) # 2 - 二维数组
# 3. size属性:元素总数
# 返回整数,表示数组中所有元素的个数
print('大小:', arr.size) # 6 - 2×3=6个元素
# 4. dtype属性:数据类型
# 返回numpy.dtype对象,描述数组的数据类型
print('数据类型:', arr.dtype) # int32或int64
# 5. reshape():改变数组形状
# (3, 2)表示重塑为3行2列
# 元素总数必须保持不变(6个)
arr_reshaped = arr.reshape(3, 2)
print('\n重塑后(3x2):')
print(arr_reshaped)代码深度解析:
shape属性的详细解释:
# 3维数组的shape arr_3d = np.random.randn(4, 5, 6) print(arr_3d.shape) # (4, 5, 6) # 含义: # - 第0维(深度): 4个元素 # - 第1维(行): 每个深度5行 # - 第2维(列): 每行6列 # 总元素数: 4×5×6 = 120 # 维度命名(约定俗成): # - axis=0: 第0维,对3维数组是"深度" # - axis=1: 第1维,对3维数组是"行" # - axis=2: 第2维,对3维数组是"列" # 沿不同维度的操作 print(f'沿axis=0求和:\n{arr_3d.sum(axis=0)}') # (5, 6) print(f'沿axis=1求和:\n{arr_3d.sum(axis=1)}') # (4, 6) print(f'沿axis=2求和:\n{arr_3d.sum(axis=2)}') # (4, 5)ndim与维度的关系:
# 0维数组(标量) scalar = np.array(5) print(scalar.ndim) # 0 # 1维数组(向量) vector = np.array([1, 2, 3]) print(vector.ndim) # 1 # 2维数组(矩阵) matrix = np.array([[1, 2], [3, 4]]) print(matrix.ndim) # 2 # 3维数组(张量) tensor = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) print(tensor.ndim) # 3 # 为什么维度很重要? # - 维度决定了数据的组织方式 # - 不同维度的操作有不同的axis参数 # - 高维数据需要特殊的处理方法size与内存计算:
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) # size返回元素个数 print(arr.size) # 6 # nbytes返回字节数 print(arr.nbytes) # 24 = 6个元素 × 4字节/元素 # 计算内存占用(MB) memory_mb = arr.nbytes / (1024 * 1024) print(f'内存占用: {memory_mb:.3f} MB') # 大规模数组的内存估算 # 假设1000×1000的float64数组 large_arr = np.zeros((1000, 1000), dtype=np.float64) memory_mb = large_arr.nbytes / (1024 * 1024) print(f'1000×1000数组占用: {memory_mb:.2f} MB') # 输出: 7.63 MB # 这就是为什么NumPy适合大规模计算 # 相比之下,Python列表会占用数十倍内存dtype的深入理解:
# NumPy支持丰富的数据类型 # 整数类型: int8, int16, int32, int64 # 无符号整数: uint8, uint16, uint32, uint64 # 浮点类型: float16, float32, float64 # 复数类型: complex64, complex128 # 整数类型的范围 print(f'int8范围: [{np.iinfo(np.int8).min}, {np.iinfo(np.int8).max}]') print(f'int16范围: [{np.iinfo(np.int16).min}, {np.iinfo(np.int16).max}]') # 浮点类型的精度 print(f'float32精度: {np.finfo(np.float32).eps}') # 约1.2e-7 print(f'float64精度: {np.finfo(np.float64).eps}') # 约2.2e-16 # 金融应用:为什么要用float64? # - 金融计算需要高精度 # - 避免累积误差 # - 符合行业标准(IEEE 754) # 何时可以用float32? # - 内存受限时(如图像处理) # - 精度要求不高时 # - 性能优先时(float32运算更快)reshape的高级用法:
# 展平数组 arr = np.array([[1, 2, 3], [4, 5, 6]]) # 方法1:ravel()返回视图(view) flat_view = arr.ravel() flat_view[0] = 100 print(arr[0, 0]) # 100 - 原数组也被修改! # 方法2:flatten()返回副本(copy) flat_copy = arr.flatten() flat_copy[0] = 200 print(arr[0, 0]) # 100 - 原数组不受影响 # 多维重塑 arr = np.arange(24) arr_3d = arr.reshape(2, 3, 4) # 2×3×4的3维数组 print(arr_3d.shape) # transpose()交换维度 # (2, 3, 4) -> (4, 3, 2) arr_transposed = arr_3d.transpose(2, 1, 0) print(arr_transposed.shape)
47.6 数组运算
# 创建两个一维数组
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 1. 基本算术运算
# NumPy的运算是元素级的(element-wise)
# 即对应位置的两个元素分别运算
print('加法:', a + b)
# 输出: [5 7 9] # [1+4, 2+5, 3+6]
print('减法:', a - b)
# 输出: [-3 -3 -3] # [1-4, 2-5, 3-6]
print('乘法:', a * b)
# 输出: [4 10 18] # [1×4, 2×5, 3×6]
# 注意:这不是矩阵乘法!
print('除法:', a / b)
# 输出: [0.25 0.4 0.5 ] # [1/4, 2/5, 3/6]
print('幂运算:', a ** 2)
# 输出: [1 4 9] # [1², 2², 3²]
# 2. 统计运算
# 这些是NumPy数组的方法,对整个数组进行统计
print(f'\n均值: {a.mean():.2f}')
# 输出: 2.00 # (1+2+3)/3
print(f'标准差: {a.std():.2f}')
# 输出: 0.82 # √[((1-2)²+(2-2)²+(3-2)²]/3]
print(f'方差: {a.var():.2f}')
# 输出: 0.67 # [(1-2)²+(2-2)²+(3-2)²]/3
print(f'最大值: {a.max()}')
# 输出: 3
print(f'最小值: {a.min()}')
# 输出: 1
# 求和
print(f'求和: {a.sum()}')
# 输出: 6代码深度解析:
向量化运算的数学意义:
# Python列表运算 vs NumPy数组运算 # Python列表:元素级相加需要循环 list_a = [1, 2, 3] list_b = [4, 5, 6] # 方法1:for循环 result = [] for i in range(len(list_a)): result.append(list_a[i] + list_b[i]) # 方法2:列表推导式 result = [list_a[i] + list_b[i] for i in range(len(list_a))] # NumPy数组:向量化运算 arr_a = np.array([1, 2, 3]) arr_b = np.array([4, 5, 6]) result = arr_a + arr_b # 一行代码! # 性能差异: # - Python循环:解释执行,每个元素都要类型检查 # - NumPy向量化:C层面,直接对内存块操作 # 对于100万个元素,NumPy可能快100倍广播机制(Broadcasting):
# 广播:不同形状的数组可以运算 # 规则:从尾部(最右边)开始,逐维度比较 # 示例1:标量与数组 scalar = 5 arr = np.array([1, 2, 3]) result = scalar + arr print(result) # [6, 7, 8] # 标量5扩展为[5, 5, 5] # 示例2:不同维度的数组 arr1 = np.array([[1], [2], [3]]) # (3, 1) arr2 = np.array([10, 20, 30]) # (3,) result = arr1 + arr2 print(result) # arr1扩展为[[1,1,1], [2,2,2], [3,3,3]] # arr2扩展为[[10,20,30], [10,20,30], [10,20,30]] # 相加后: [[11,21,31], [12,22,32], [13,23,33]] # 示例3:不兼容的形状 arr1 = np.array([1, 2, 3]) # (3,) arr2 = np.array([10, 20]) # (2,) # result = arr1 + arr2 # 错误!形状不兼容矩阵乘法 vs 元素乘法:
# 元素乘法(*):对应元素相乘 a = np.array([[1, 2], [3, 4]]) b = np.array([[5, 6], [7, 8]]) element_mul = a * b print('元素乘法:') print(element_mul) # [[ 5, 12], # [21, 32]] # 矩阵乘法(@):线性代数的矩阵乘法 mat_mul = a @ b # 或 np.dot(a, b) print('\n矩阵乘法:') print(mat_mul) # [[1×5+2×7, 1×6+2×8], # [3×5+4×7, 3×6+4×8]] # = [[19, 22], # [43, 50]] # 金融应用:投资组合收益 # weights: (3,) 权重向量 # returns: (3, 5) 3只股票5天的收益率矩阵 weights = np.array([0.3, 0.5, 0.2]) returns = np.random.randn(3, 5) # 每天的投资组合收益 portfolio_returns = weights @ returns print(f'组合收益率: {portfolio_returns}')统计函数的axis参数:
arr = np.array([ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]) # 不指定axis:对所有元素求统计量 print(f'全局均值: {arr.mean()}') # 5.0 # axis=0:沿列方向(垂直) print(f'列均值(向下): {arr.mean(axis=0)}') # [4., 5., 6.] # 每列的均值 # axis=1:沿行方向(水平) print(f'行均值(向右): {arr.mean(axis=1)}') # [2., 5., 8.] # 每行的均值 # 累积运算 print(f'累积和(沿行):') print(arr.cumsum(axis=1)) # [[ 1, 3, 6], # [ 4, 9, 15], # [ 7, 15, 24]] # 金融应用:计算累计收益率 # daily_returns是日收益率序列 daily_returns = np.array([0.01, -0.02, 0.03, 0.01, -0.01]) # 累计收益率 = (1+r1)×(1+r2)×...×(1+rn) - 1 cumulative_returns = (1 + daily_returns).cumprod() - 1 print(f'累计收益率: {cumulative_returns}')比较运算与布尔索引:
arr = np.array([1, 2, 3, 4, 5]) # 比较运算返回布尔数组 mask = arr > 3 print(f'布尔掩码: {mask}') # [False, False, False, True, True] # 使用布尔索引筛选元素 filtered = arr[mask] print(f'筛选结果: {filtered}') # [4, 5] # 金融应用:找出上涨的交易日 returns = np.array([0.01, -0.02, 0.03, -0.01, 0.02]) # 正收益的掩码 positive_mask = returns > 0 print(f'正收益掩码: {positive_mask}') # 统计正收益天数 positive_days = np.sum(positive_mask) print(f'正收益天数: {positive_days}/5') # 提取正收益 positive_returns = returns[positive_mask] print(f'正收益率: {positive_returns}')
47.7 金融应用收益率计算
# 价格序列
# 假设5个交易日的收盘价
prices = np.array([100, 105, 103, 108, 106])
# 1. 计算简单收益率(Simple Returns)
# 公式: R_t = (P_t - P_{t-1}) / P_{t-1}
# 使用切片:prices[1:]取第1到最后元素
# prices[:-1]取第0到倒数第2个元素
# 这样两个数组的长度相同,可以对应计算
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]
# 2. 计算对数收益率(Log Returns)
# 公式: r_t = ln(P_t / P_{t-1}) = ln(1 + R_t)
# 对数收益率具有时间可加性
log_returns = np.log(prices[1:] / prices[:-1])
print('简单收益率:')
print(simple_returns)
print('\n对数收益率:')
print(log_returns)
# 3. 计算累计收益率
# 公式: 累计收益率 = (1+R_1)×(1+R_2)×...×(1+R_n) - 1
# cumprod()计算累积乘积
cum_returns = (1 + simple_returns).cumprod() - 1
print(f'\n累计收益率:')
print(cum_returns)
print(f'\n总收益率: {cum_returns[-1]:.4f}')
# 输出最后一个值(总收益率)代码深度解析:
切片操作的数学意义:
prices = np.array([100, 105, 103, 108, 106]) # prices[1:]: 取索引1到末尾 # Python切片语法: [start:stop:step] # 省略start表示从0开始 # 省略stop表示到末尾 prices_1_to_end = prices[1:] # [105, 103, 108, 106] # prices[:-1]: 取索引0到倒数第2个 # -1表示最后一个元素,所以:-1表示不包含最后一个 prices_0_to_2last = prices[:-1] # [100, 105, 103, 108] # 对应关系: # prices_1_to_end[0] = 105 <- prices_0_to_2last[1] # prices_1_to_end[1] = 103 <- prices_0_to_2last[2] # ...简单收益率 vs 对数收益率:
# 简单收益率 R = 0.05 # 缺点:不可跨时间相加 # 两期收益率: R_total ≠ R_1 + R_2 # 对数收益率 r = np.log(1 + R) # 优点:可跨时间相加 # 两期收益率: r_total = r_1 + r_2 # 验证 prices = np.array([100, 110, 105]) # 方法1:简单收益率的复合 R1 = (110 - 100) / 100 # 0.10 R2 = (105 - 110) / 110 # -0.045... total_simple = (1 + R1) * (1 + R2) - 1 # 0.05 print(f'简单收益率复合: {total_simple:.4f}') # 0.0500 # 方法2:对数收益率的相加 r1 = np.log(110 / 100) r2 = np.log(105 / 110) total_log = r1 + r2 print(f'对数收益率相加: {total_log:.4f}') # 0.0500 # 结果相等!但对数形式更容易处理累计收益率的计算:
# 假设日收益率序列 returns = np.array([0.01, -0.02, 0.03, 0.01, -0.01]) # 累计收益率的几何含义 # 初始资金: 100元 # 第1天后: 100 × (1+0.01) = 101 # 第2天后: 101 × (1-0.02) = 98.98 # 第3天后: 98.98 × (1+0.03) = 101.95 # ... # NumPy的cumprod()计算累积乘积 cumprod = (1 + returns).cumprod() print('累计乘积因子:') print(cumprod) # 累计收益率 = 累计乘积因子 - 1 cum_returns = cumprod - 1 print('\n累计收益率:') print(cum_returns) # 验证 initial = 100 final = initial * cumprod[-1] print(f'\n初始100元最终: {final:.2f}元') # 最大回撤计算 # 最大回撤 = (峰值 - 当前值) / 峰值 cum_returns_with_initial = np.insert(cumprod, 0, 1.0) peak = np.maximum.accumulate(cum_returns_with_initial) drawdown = (peak - cum_returns_with_initial) / peak max_drawdown = drawdown.max() print(f'最大回撤: {max_drawdown:.2%}')年化收益率与波动率:
# 假设有252个交易日的收益率数据 np.random.seed(42) # 设置随机种子,保证结果可复现 daily_returns = np.random.normal(0.0005, 0.02, 252) # 年化收益率 # 简单方法: 日均值 × 252 annualized_return = daily_returns.mean() * 252 print(f'年化收益率: {annualized_return:.2%}') # 年化波动率 # 日标准差 × √252 annualized_vol = daily_returns.std() * np.sqrt(252) print(f'年化波动率: {annualized_vol:.2%}') # 夏普比率(假设无风险利率为3%) risk_free_rate = 0.03 sharpe_ratio = (annualized_return - risk_free_rate) / annualized_vol print(f'夏普比率: {sharpe_ratio:.2f}') # 索提诺比率(只考虑下行波动率) # 只取负收益计算标准差 negative_returns = daily_returns[daily_returns < 0] downside_std = negative_returns.std() * np.sqrt(252) sortino_ratio = (annualized_return - risk_free_rate) / downside_std print(f'索提诺比率: {sortino_ratio:.2f}')收益率的统计特性:
# 正态性检验(JB检验) from scipy import stats # 生成收益率数据 np.random.seed(42) returns = np.random.normal(0.001, 0.02, 1000) # JB检验:原假设是数据来自正态分布 # p-value>0.05:不能拒绝原假设(可能来自正态分布) # p-value<0.05:拒绝原假设(不太可能来自正态分布) statistic, p_value = stats.jarque_bera(returns) print(f'JB统计量: {statistic:.2f}') print(f'p-value: {p_value:.4f}') if p_value > 0.05: print('收益率可能服从正态分布') else: print('收益率不服从正态分布') # 金融意义: # - 如果收益率服从正态分布,可以用传统参数方法 # - 如果不服从,需要使用非参数方法或考虑厚尾分布
最佳实践总结:
- 选择合适的数据类型:
- 金融计算优先使用
float64(双精度) - 大数据集考虑
float32(单精度)以节省内存 - 整数数据选择足够大的类型避免溢出
- 金融计算优先使用
- 充分利用向量化:
- 避免Python循环,使用NumPy内置函数
- 利用广播机制进行数组运算
- 使用布尔索引替代条件判断
- 内存优化技巧:
- 使用
reshape()的-1参数自动推断 - 使用
ravel()而非flatten()避免复制 - 及时删除不再需要的大数组
- 使用
- 数值稳定性:
- 对数收益率避免小数精度问题
- 使用
np.log1p(x)计算log(1+x),提高小数值精度 - 除法前检查分母是否为零
- 性能优化:
- 预分配数组空间,避免动态扩展
- 使用
np.out参数存储中间结果 - 对大规模数据使用分块计算